classdef SQLstatement < handle
  % Super-class for SQL statements
  % Derived from handle
  % C-tor sig: SQLobject = SQLstatement(conn), arg required
  %     conn: a DBwrapper-wrapped Matlab database object, the recipient of 
  %           statements; may be empty
  %
  % Author: David Goldsmith, Wash. State Dept. of Ecology, dgol461@ecy.wa.gov
  % Release date: N/A
  
    properties
        wrappedDBO;
        con; cat; sch; meta;
        tbls = {};
        aCc = {}; % cell array of available columns, w/out info as to their source table(s)
        pkey;
        result;
        issuedStatementError = false;
        issuedAvailableError = false;
        errCode = {};
        duration;
    end
    
    properties (Dependent = true)
        DBname;
        statement;
        availableTables;
        availableColumns;
    end
    
    methods 
        function SQLobject = SQLstatement(conn)
            if ~isempty(conn), SQLobject.setUp(conn); end
        end
        
        function setUp(obj, conn)
            obj.wrappedDBO = conn;
            obj.con = conn.DB;
            if ~isempty(obj.con.Message)
                obj.errCode{end+1} = obj.con.Message;
            end
            try
                obj.cat = get(obj.con, 'Catalog');
                obj.sch = schemas(obj.con);
                obj.meta = dmd(obj.con);
            catch me
                obj.errCode{end+1} = getReport(me);
            end
        end
        
        function name = get.DBname(obj)
            try
                ping(obj.con)
                name = obj.con.Instance;
            catch
                name = ['Development Version: database connection not '...
                        'established on purpose.'];
            end
        end
        
        function S = get.statement(obj)
            try
                S = obj.assembleStatement;
            catch me
                if obj.issuedStatementError
                    S = getReport(me);
                    obj.issuedStatementError = false;
                else
                    obj.errCode{end+1} = ['Sorry, attempt to assemble '...
                                          'statement failed; more info is '...
                                          'in this object''s "statement" '...
                                          'field.  '];
                    obj.issuedStatementError = true;
                end
            end
        end
        
        function aT = get.availableTables(obj)
            try
                aT = tables(obj.con, obj.cat);
            catch me
                aT = getReport(me);
                if obj.issuedAvailableError
                    obj.issuedAvailableError = false;
                else
                    obj.errCode{end+1} = ['Sorry, attempt to get available '...
                                          'tables failed; more info is in '...
                                          'this object''s "availableTables" '...
                                          'field.  '];
                    obj.issuedAvailableError = true;
                end
            end
        end
        
        function aC = get.availableColumns(obj)
            aC = struct.empty; obj.aCc = {};
            if ~isempty(obj.tbls)
                for t = obj.tbls
                    t = t{:};
                    for s = obj.sch
                        if ismember(t, tables(obj.con, obj.cat, s))
                            cols = columns(obj.con, obj.cat, s, t);
                            aC(1).(t) = cols;
                            obj.aCc = extendArray(obj.aCc, cols);
                            break;
                        end
                    end
                end
                obj.aCc = unique(obj.aCc);
            end
        end    

        function getTablesPKey(obj)
            if ~isempty(obj.tbls)
                obj.sch = getTablesSchema(obj.con, obj.cat, obj.tbls{1});
                  % Above assumes all the tables have the same schema =>
                  % potential bug; TODO: fix
                tmp = primarykeys(obj.meta, obj.cat, obj.sch, obj.tbls{:});
                if ~(isempty(tmp) || (numel(tmp) < 4))
                    obj.pkey = tmp{4}; % Primary Key column name
                else
                    obj.errCode{end+1} = ['SQLstatement.getTablesPKey '...
                                          'error: primarykeys function '...
                                          'returned too few results.  '];
                end
            else
                obj.errCode{end+1} = ['SQLstatement.getTablesPKey error: '...
                                      'tbls property empty; can''t get PK '...
                                      'without at least one table.  '];
            end
        end
        
        function delete(obj)
          % Here to suppress warning
        end
                
    end
    
    methods (Abstract)
        S = assembleStatement(obj);
        submitStatement(obj);
        setPropVals(obj, prop, elements, extend);
    end
    
end